<!doctype html>
<html>
    <head>
        <meta content="text/html; charset=utf-8" http-equiv="content-type" />
        <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
        <link rel="help" href="http://dev.w3.org/html5/spec/history.html" />
        <title>HTML5 History Test Cases</title>
        <script src="/resources/testharness.js"></script>
        <script src="/resources/testharnessreport.js"></script>
    </head>
<body>
    <div id="log"></div>

    <!-- Use this iframe to test url changes so that the base url does not change. Their document does not matter. -->
    <iframe id="testframe1" style="display:none" src="/common/blank.html"></iframe>
    <iframe id="testframe2" style="display:none" src="/common/blank.html"></iframe>

    <script type="text/javascript">
        var testCollection;
        var testIndex = 0;
        var testframe1 = document.getElementById("testframe1");
        var testframe2 = document.getElementById("testframe2");

        setup(function()
        {
            testCollection = [
                function() {
                     test(function() {
                        assert_own_property(window, "onpopstate", "window has 'onpopstate' own property");
                     }, "onpopstate in window");
                },
                function() {
                     test(function() {
                        assert_inherits(window.history, "pushState", "history inherits property 'pushState'");
                        assert_equals(window.history.pushState.constructor, Function, "pushState is a function");
                     }, "history.pushState is present");
                 },
                 function() {
                     test(function() {
                        assert_inherits(window.history, "replaceState", "history inherits property 'replaceState'");
                        assert_equals(window.history.replaceState.constructor, Function, "replaceState is a function");
                     }, "history.replaceState is present");
                 },
                 function() {
                     test(function() {
                        assert_inherits(window.history, "state", "history inherits property 'state'");
                     }, "history.state is present");
                },
                function() {
                     test(function() {
                        assert_equals(window.history.state, null, "history.state initialized to null");
                     }, "history.state is initialized to null");
                },

                function() {
                    test(function() {
                        var length = history.length;
                        history.pushState(null,null);
                        assert_equals(history.length, length+1, "history.length should be incremented by one");
                    }, "history.pushState increments history.length");
                },

                function() {
                    var t = async_test("history.pushState clears forward entries");
                    t.step(function() {
                        var length = history.length;
                        //push some extra entries into the session history
                        history.pushState(null,null);
                        history.pushState(null, null);
                        history.pushState(null, null);

                        //there should now be three extra
                        assert_equals(history.length, length+3, "Three additional travel entries add to history.length");

                        let popstateCount = 0;
                        onpopstate = t.step_func(function(e) {
                          if (++popstateCount == 3) {
                            onpopstate = null;
                            //once the .back navigations have completed, push again and verify length is one more than starting value
                            history.pushState(null, null);
                            assert_equals(history.length, length+1, "History.length should now only be one more than original value");
                            t.done();
                          }
                        });
                        //travel back to the entry that the test started on
                        history.back();
                        history.back();
                        history.back();
                    });
                },

                function() {
                    test(function() {
                        testframe1.contentWindow.history.pushState(null,null, "test-pushstate-url");
                        assert_equals(getPageName(testframe1.contentWindow.location.href), "test-pushstate-url", "iframe1 has the pushed url");
                    }, "history.pushState accepts a third parameter 'url' and uses it to alter location");
                },
                function() {
                    test(function() {
                        var oldurl = testframe1.contentWindow.location.href.toString();
                        var pagename = getPageName(oldurl);
                        //form a new absolute url (with protocol, host, etc) with "absolute-page" as the name of the page
                        var newurl = oldurl.replace(pagename, "absolute-page");

                        testframe1.contentWindow.history.pushState(null,null, newurl);
                        assert_equals(testframe1.contentWindow.location.href, newurl, "iframe1 has the pushed url correctly");
                    }, "history.pushState's url parameter can be an absolute url");
                },

                function() {
                    test(function() {
                        testframe1.contentWindow.history.pushState(null,null, "multiple-pushstate-url1");
                        testframe2.contentWindow.history.pushState(null,null, "multiple-pushstate-url2");

                        assert_equals(getPageName(testframe1.contentWindow.location.href), "multiple-pushstate-url1", "iframe1 has the pushed url");
                        assert_equals(getPageName(testframe2.contentWindow.location.href), "multiple-pushstate-url2", "iframe2 has the pushed url");
                    }, "history.pushState can modify location object in multiple frames");
                },

                function() {
                    test(function() {
                        //trigger a security error by replacing the host of the current url with a fake one that is cross-domain
                        var testurl = testframe1.contentWindow.location.href.toString().replace(testframe1.contentWindow.location.host, "fakelocation-push");
                        assert_throws_dom("SECURITY_ERR", function() { history.pushState(null, null, testurl); }, "Security_Err 18 should be thrown");
                    }, "history.pushState throws DOMException with code SECURITY_ERR (18)");
                },

                function() {
                    test(function() {
                        //trigger a data clone error by passing invalid SCA data into the function
                        assert_throws_dom("DATA_CLONE_ERR", function() { history.pushState(document.body, null); }, "pushState should throw an exception DATA_CLONE_ERR with code 25");
                    }, "history.pushState throws DATA_CLONE_ERR(25) for bad state parameter");
                },

                function() {
                    test(function() {
                        var length = history.length;
                        history.replaceState(null,null);
                        assert_equals(history.length, length, "history.length should not change");
                    }, "history.replaceState does not increment history.length");
                },

                function() {
                    var t = async_test("history.replaceState does not clear forward entries");
                    t.step(function() {
                        var length = history.length;
                        //push some extra entries into the session history
                        history.pushState(null,null);
                        history.pushState(null, null);
                        history.pushState(null, null);

                        //there should now be three extra
                        assert_equals(history.length, length+3, "Three additional travel entries add to history.length");

                        let popstateCount = 0;
                        onpopstate = t.step_func(function(e) {
                          if (++popstateCount == 2) {
                            onpopstate = null;
                            //once the .back navigations have fired, replace and verify the length has not changed since the last check
                            history.replaceState(null, null);
                            assert_equals(history.length, length+3, "History.length should still be three more than original value");
                            t.done();
                          }
                        });

                        //travel back two entries to land in the middle
                        history.back();
                        history.back();
                    });
                },

                function() {
                    test(function() {
                        testframe1.contentWindow.history.replaceState(null,null, "test-replaceState-url");
                        assert_equals(getPageName(testframe1.contentWindow.location.href), "test-replaceState-url", "iframe1 has the pushed url");
                    }, "history.replaceState accepts a third parameter 'url' and uses it to alter location");
                },
                function() {
                    test(function() {
                        var oldurl = testframe1.contentWindow.location.href.toString();
                        var pagename = getPageName(oldurl);
                        //form a new absolute url (with protocol, host, etc) with "absolute-page" as the name of the page
                        var newurl = oldurl.replace(pagename, "absolute-page");

                        testframe1.contentWindow.history.replaceState(null,null, newurl);
                        assert_equals(testframe1.contentWindow.location.href, newurl, "iframe1 has the pushed url correctly");
                    }, "history.replaceState's url parameter can be an absolute url");
                },

                function() {
                    test(function() {
                        testframe1.contentWindow.history.replaceState(null,null, "multiple-replaceState-url1");
                        testframe2.contentWindow.history.replaceState(null,null, "multiple-replaceState-url2");

                        assert_equals(getPageName(testframe1.contentWindow.location.href), "multiple-replaceState-url1", "iframe1 has the pushed url");
                        assert_equals(getPageName(testframe2.contentWindow.location.href), "multiple-replaceState-url2", "iframe2 has the pushed url");
                    }, "history.replaceState can modify location object in multiple frames");
                },

                function() {
                    test(function() {
                        //trigger a security error by replacing the host of the current url with a fake one that is cross-domain
                        var testurl = testframe1.contentWindow.location.href.toString().replace(testframe1.contentWindow.location.host, "fakelocation-replace");
                        assert_throws_dom("SECURITY_ERR", function() { history.replaceState(null, null, testurl); }, "Security_Err 18 should be thrown");
                    }, "history.replaceState throws DOMException with code SECURITY_ERR (18)");
                },

                function() {
                    test(function() {
                        //trigger a data clone error by passing invalid SCA data into the function
                        assert_throws_dom("DATA_CLONE_ERR", function() {history.replaceState(document.body, null);}, "replaceState should throw an exception DATA_CLONE_ERR with code 25");
                    }, "history.replaceState throws DATA_CLONE_ERR(25) for bad state parameter");
                },

                function() {
                    var t = async_test("PopStateEvent fires on Back navigation");
                    t.step(function() {
                        history.pushState(null, null);
                        history.pushState(null, null);
                        //prepare to end the test as soon as popstate fires
                        onpopstate = function(e) {
                            t.done();
                        }
                        //go back to fire the popstate event
                        history.back();
                    });
                },

                function() {
                    var t = async_test("PopStateEvent fires on Forward navigation");
                    t.step(function() {
                        onpopstate = null;
                        history.pushState(null, null);
                        history.pushState(null, null);
                        onpopstate = function(e) {
                            //prepare to end the test as soon as popstate fires
                            onpopstate = function(e) {
                                t.done();
                            };
                            //go forward to fire the popstate event
                            history.forward();
                        };
                        history.back();
                    });
                },

                function() {
                    var t = async_test("PopStateEvent receives state data on Back navigation");
                    t.step(function() {
                        history.pushState("popstate-data", null);
                        history.pushState(null, null);
                        //prepare the popstate event to validate the data
                        onpopstate = t.step_func(function(e) {
                            assert_equals(e.state, "popstate-data", "State data is passed to the event correctly");
                            t.done();
                        });
                        //go back to fire the popstate event
                        history.back();
                    });
                },

                function() {
                    test(function() {
                        history.pushState("pushstate-data", null);
                        //history.state should be set immediately
                        assert_equals(history.state, "pushstate-data", "State data is set correctly");
                    }, "history.state is set by history.pushState");
                },

                function() {
                    test(function() {
                        history.replaceState("replacestate-data", null);
                        //history.state should be set immediately
                        assert_equals(history.state, "replacestate-data", "State data is set correctly");
                    }, "history.state is set by history.replaceState");
                },

                function() {
                    var t = async_test("history.state changes on navigation");
                    t.step(function() {
                        history.pushState("state-back1", null);
                        history.pushState("state-back2", null);
                        //precondition
                        assert_equals(history.state, "state-back2", "Verify that history.state is set to a second value");

                        //set up the popstate event to verify that history.state was changed
                        onpopstate = t.step_func(function(e) {
                            assert_equals(e.state, "state-back1", "Verify that history.state reverted to the first value");
                            t.done();
                        });
                        history.back();
                    });
                },
            ];
        }, {explicit_done:true, timeout:8000});

        //used to get the name of a page within a path
        //  to check correctness of url parameter
        function getPageName(path) {
            var path = path || location.pathname;
            var segments = path.split('/');
            return segments[segments.length-1];
        }

        //Callback for result_callback
        //queues the next test in the array testCollection
        //serves to make test execution sequential despite asynchronous behaviors
        function testFinished(test) {
            if(testIndex < testCollection.length - 1) {
                //Run the function asynchronously so that the stack remains shallow
                setTimeout(testCollection[++testIndex]);
            } else {
                //when the last test has run, explicitly end test suite
                done();
            }
        }

        add_result_callback(testFinished);

        function startTestsWhenIframesLoaded() {
          if (testframe1.contentWindow.document.readyState != 'complete' ||
              testframe2.contentWindow.document.readyState != 'complete') {
            return;
          }
          testframe1.removeEventListener('load', startTestsWhenIframesLoaded, false);
          testframe2.removeEventListener('load', startTestsWhenIframesLoaded, false);

          //start the first test
          setTimeout(testCollection[testIndex]);
        }

        // add listeners to trigger the tests once the iframes are loaded,
        // but also try to start it right away in case the load events have
        // already fired and we missed them.
        testframe1.addEventListener('load', startTestsWhenIframesLoaded, false);
        testframe2.addEventListener('load', startTestsWhenIframesLoaded, false);
        startTestsWhenIframesLoaded();
     </script>
</body>
</html>
